package com.knightcloud.micro.auth.config;

import cn.hutool.core.date.DatePattern;
import com.knightcloud.common.base.vo.response.R;
import com.knightcloud.micro.auth.config.oauth2.OAuth2PasswordAuthenticationConverter;
import com.knightcloud.micro.auth.config.oauth2.OAuth2PasswordAuthenticationProvider;
import com.knightcloud.micro.upms.api.dto.SysUserInfoDto;
import com.knightcloud.micro.upms.api.feign.RemoteUserService;
import lombok.AllArgsConstructor;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.core.OAuth2Token;
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationContext;
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import org.springframework.security.oauth2.server.authorization.web.authentication.DelegatingAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2ClientCredentialsAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2RefreshTokenAuthenticationConverter;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.RequestMatcher;

import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

import static org.springframework.security.config.Customizer.withDefaults;

/**
 * 授权服务配置
 *
 * @author knight
 */
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
@AllArgsConstructor
public class AuthorizationServerConfig {

	private static final String CUSTOM_CONSENT_PAGE_URI = "/oauth2/consent";

	/**
	 * 用户详细信息服务
	 */
	private final UserDetailsService userDetailsService;

	/**
	 * 远程用户服务
	 */
	private final RemoteUserService remoteUserService;

	/**
	 * 授权服务器安全过滤器链
	 * @param http http
	 * @return {@link SecurityFilterChain}
	 * @throws Exception 异常
	 */
	// @formatter:off
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
        OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
                new OAuth2AuthorizationServerConfigurer();
        authorizationServerConfigurer
                .authorizationEndpoint(authorizationEndpoint ->
                        // 自定义consent页面
                        authorizationEndpoint.consentPage(CUSTOM_CONSENT_PAGE_URI))
				.tokenEndpoint(tokenEndpoint -> tokenEndpoint
						.accessTokenRequestConverter(new DelegatingAuthenticationConverter(Arrays.asList(
								new OAuth2AuthorizationCodeAuthenticationConverter(),
								new OAuth2RefreshTokenAuthenticationConverter(),
								new OAuth2ClientCredentialsAuthenticationConverter(),
								new OAuth2PasswordAuthenticationConverter()
						))))
				.oidc(oidcConfigurer -> oidcConfigurer.userInfoEndpoint(oidcUserInfoEndpointConfigurer ->
					oidcUserInfoEndpointConfigurer.userInfoMapper(oidcUserInfoFunction())));

        RequestMatcher endpointsMatcher = authorizationServerConfigurer
                .getEndpointsMatcher();

        http
                .securityMatcher(endpointsMatcher)
                .authorizeHttpRequests(authorizeRequests ->
                        authorizeRequests.anyRequest().authenticated()
                )
				.oauth2ResourceServer(oauth2 -> oauth2.opaqueToken(Customizer.withDefaults()))
                .csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher))
                .apply(authorizationServerConfigurer);
		SecurityFilterChain securityFilterChain = http.formLogin(Customizer.withDefaults()).build();
		// 添加自定义oauth2密码身份验证提供者
		addCustomOAuth2PasswordAuthenticationProvider(http);
		return securityFilterChain;
	}
	// @formatter:on

	@Bean
	@Order(SecurityProperties.BASIC_AUTH_ORDER)
	SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
		http.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()).formLogin(withDefaults());
		return http.build();
	}

	/**
	 * 添加自定义oauth2密码身份验证提供者
	 * @param http http
	 */
	private void addCustomOAuth2PasswordAuthenticationProvider(HttpSecurity http) {
		OAuth2AuthorizationService authorizationService = http.getSharedObject(OAuth2AuthorizationService.class);
		OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator = http.getSharedObject(OAuth2TokenGenerator.class);

		// 构建OAuth2PasswordAuthenticationProvider
		OAuth2PasswordAuthenticationProvider passwordAuthenticationProvider = new OAuth2PasswordAuthenticationProvider(
				userDetailsService, authorizationService, tokenGenerator);
		http.authenticationProvider(passwordAuthenticationProvider);
	}

	/**
	 * 授权同意服务
	 * @param jdbcTemplate jdbc模板
	 * @param registeredClientRepository 注册客户端库
	 * @return {@link OAuth2AuthorizationConsentService}
	 */
	@Bean
	public OAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate,
			RegisteredClientRepository registeredClientRepository) {
		return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository);
	}

	/**
	 * 授权服务
	 * @param jdbcTemplate jdbc模板
	 * @param registeredClientRepository 注册客户端库
	 * @return {@link OAuth2AuthorizationService}
	 */
	@Bean
	public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate,
			RegisteredClientRepository registeredClientRepository) {
		return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
	}

	/**
	 * jwtTokenCustomizer
	 * @see org.springframework.security.oauth2.server.authorization.token.JwtGenerator
	 * @return {@link OAuth2TokenCustomizer}<{@link JwtEncodingContext}>
	 */
	@Bean
	public OAuth2TokenCustomizer<JwtEncodingContext> jwtTokenCustomizer() {
		return context -> {
			Authentication principal = context.getPrincipal();
			if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType())
					&& principal instanceof UsernamePasswordAuthenticationToken) {
				Set<String> authorities = principal.getAuthorities()
					.stream()
					.map(GrantedAuthority::getAuthority)
					.collect(Collectors.toSet());
				context.getClaims().claim("authorities", authorities);
			}
		};
	}

	/**
	 * opaqueTokenCustomizer
	 * @see org.springframework.security.oauth2.server.authorization.token.OAuth2AccessTokenGenerator
	 * @return {@link OAuth2TokenCustomizer}<{@link OAuth2TokenClaimsContext}>
	 */
	@Bean
	public OAuth2TokenCustomizer<OAuth2TokenClaimsContext> opaqueTokenCustomizer() {
		return context -> {
			Authentication principal = context.getPrincipal();
			if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType())
					&& principal instanceof UsernamePasswordAuthenticationToken) {
				Set<String> authorities = principal.getAuthorities()
					.stream()
					.map(GrantedAuthority::getAuthority)
					.collect(Collectors.toSet());
				context.getClaims().claim("authorities", authorities);
			}
		};
	}

	/**
	 * oidc用户信息函数
	 * @return {@link Function}<{@link OidcUserInfoAuthenticationContext},
	 * {@link OidcUserInfo}>
	 */
	public Function<OidcUserInfoAuthenticationContext, OidcUserInfo> oidcUserInfoFunction() {
		return oidcUserInfoAuthenticationContext -> {
			String username = oidcUserInfoAuthenticationContext.getAuthorization().getPrincipalName();
			R<SysUserInfoDto> sysUserInfoDtoR = remoteUserService.selectSysUserInfoByUsername(username);
			if (!sysUserInfoDtoR.isSuccess()) {
				return null;
			}
			SysUserInfoDto userInfoDto = sysUserInfoDtoR.getData();
			return OidcUserInfo.builder()
				.preferredUsername(username)
				.name(userInfoDto.getName())
				.profile(userInfoDto.getAvatar())
				.gender(String.valueOf(userInfoDto.getGender()))
				.phoneNumber(userInfoDto.getPhone())
				.email(userInfoDto.getEmail())
				.birthdate(Objects.nonNull(userInfoDto.getBirth())
						? userInfoDto.getBirth().format(DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN))
						: "")
				.build();
		};
	}

}
